package com.mvc.framework.dao;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.loader.criteria.CriteriaQueryTranslator;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;

import com.mvc.framework.dao.PropertyFilter.MatchType;
import com.mvc.framework.util.ReflectUtils;

public final class HibernateWebUtils {

	private HibernateWebUtils() {
	}

	public static List<PropertyFilter> buildPropertyFilters(final HttpServletRequest request) {
		return buildPropertyFilters(request, "search_");
	}

	/**
	 * eg. search_EQS_name
	 */
	public static List<PropertyFilter> buildPropertyFilters(final HttpServletRequest request,
			final String filterPrefix) {
		List<PropertyFilter> filterList = new ArrayList<PropertyFilter>();
		//Get request parameter
		Map<String, Object> filterParamMap = WebUtils.getParametersStartingWith(request, filterPrefix);
		// Construct PropertyFilter list
		for (Map.Entry<String, Object> entry : filterParamMap.entrySet()) {
			String filterName = entry.getKey();
			String value = entry.getValue().toString();
			// Ignore when parameter value is blank
			if (StringUtils.isNotBlank(value)) {
				PropertyFilter filter = new PropertyFilter(filterName, value);
				filterList.add(filter);
			}
		}
		return filterList;
	}

	public static Criteria createCriteria(final Session session, final Class entityClass, final List<PropertyFilter> propertyFilters) {
		Criteria criteria = session.createCriteria(entityClass);
		Criterion[] criterions = buildPropertyFilterCriterions(propertyFilters);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	public static Criteria createCriteria(final Session session, final Class entityClass, final Criterion... criterions) {
		Criteria criteria = session.createCriteria(entityClass);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}
	
	public static Criterion[] buildPropertyFilterCriterions(final List<PropertyFilter> filters) {
		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (PropertyFilter filter : filters) {
			if(MatchType.OR.equals(filter.getMatchType())){
				Disjunction disjunction = Restrictions.disjunction();
				for (PropertyFilter propertyFilter : filter.getOrPropertyFilters()) {
					Criterion criterion = buildPropertyFilterCriterion(propertyFilter.getPropertyName(), propertyFilter.getPropertyValue(), propertyFilter.getMatchType());
					disjunction.add(criterion);
				}
				criterionList.add(disjunction);
			}else{
				Criterion criterion = buildPropertyFilterCriterion(filter.getPropertyName(), filter.getPropertyValue(),filter.getMatchType());
				criterionList.add(criterion);
			}
		}
		return criterionList.toArray(new Criterion[criterionList.size()]);
	}

	public static Criterion buildPropertyFilterCriterion(final String propertyName, final Object propertyValue,final MatchType matchType) {
		Assert.hasText(propertyName, "propertyName can not be null");
		Criterion criterion = null;
		try {
			// Construct criterion according MatchType
			if (MatchType.EQ.equals(matchType)) {
				criterion = Restrictions.eq(propertyName, propertyValue);
			} else if (MatchType.LIKE.equals(matchType)) {
				criterion = Restrictions.like(propertyName, (String) propertyValue, MatchMode.ANYWHERE);
			} else if (MatchType.LE.equals(matchType)) {
				criterion = Restrictions.le(propertyName, propertyValue);
			} else if (MatchType.LT.equals(matchType)) {
				criterion = Restrictions.lt(propertyName, propertyValue);
			} else if (MatchType.GE.equals(matchType)) {
				criterion = Restrictions.ge(propertyName, propertyValue);
			} else if (MatchType.GT.equals(matchType)) {
				criterion = Restrictions.gt(propertyName, propertyValue);
			}else if (MatchType.NE.equals(matchType)) {
				criterion = Restrictions.ne(propertyName, propertyValue);
			}else if(MatchType.IN.equals(matchType)){
				criterion = Restrictions.isNull(propertyName);
			}else if(MatchType.NN.equals(matchType)){
				criterion = Restrictions.isNotNull(propertyName);
			}else if (MatchType.START.equals(matchType)) {
				criterion = Restrictions.like(propertyName, (String) propertyValue, MatchMode.START);
			}else if (MatchType.NLIKE.equals(matchType)){
				criterion = Restrictions.not(Restrictions.like(propertyName, (String) propertyValue, MatchMode.START));
			}
		} catch (Exception e) {
			throw ReflectUtils.convertReflectionExceptionToUnchecked(e);
		}
		return criterion;
	}
	
	public static String buildHql(final List<PropertyFilter> filters,CriteriaImpl criteria,SessionFactoryImplementor sessionFactoryImplementor){
		Criterion[] criterions = buildPropertyFilterCriterions(filters);
		return buildHql(criterions, criteria,sessionFactoryImplementor);
	}
	
	public static Object getQueryValus(PropertyFilter propertyFilter) {
		MatchType matchType = propertyFilter.getMatchType();
		Object result = propertyFilter.getPropertyValue();
		if(result instanceof String){
			switch (matchType) {
	        case LIKE:
	        case NLIKE:
	        	result = "%" + result + "%";
		        break;
	        case START:
	        	result = result + "%";
		        break;
	        default:
		        break;
	        }
		}
		return result;
	}
	
	public static Object[] getQueryParameter(final List<PropertyFilter> filters){
		List<Object> result = new ArrayList<Object>();
		for (PropertyFilter filter : filters) {
			if(MatchType.OR.equals(filter.getMatchType())){
				for (PropertyFilter propertyFilter : filter.getOrPropertyFilters()) {
					if(filter.getMatchType()!=MatchType.IN&&filter.getMatchType()!=MatchType.NN){
						result.add(getQueryValus(propertyFilter));
					}
				}
			}else{
				if(filter.getMatchType()!=MatchType.IN&&filter.getMatchType()!=MatchType.NN){
					result.add(getQueryValus(filter));
				}
			}
		}
		return result.toArray();
	}
	
	public static void setQueryParameter(final Query query , final Object... values) {
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
	}
	
	public static String buildHql(Criterion[] criterions,CriteriaImpl criteria,SessionFactoryImplementor sessionFactoryImplementor){
		StringBuilder hql = new StringBuilder(criterions.length * 20);
		CriteriaQueryTranslator translator = new CriteriaQueryTranslator(
			sessionFactoryImplementor,
			criteria,
			criteria.getEntityOrClassName(),
			CriteriaQueryTranslator.ROOT_SQL_ALIAS
		);
		for (Criterion c : criterions) {
			hql.append(" AND ").append(c.toSqlString(criteria, translator));
		}
		return hql.toString();
	}
	
	public static String convertCamelStyleToUpperCase(String camelStyleString) {
        if (StringUtils.isBlank(camelStyleString)) {
            return null;
        }
        if (camelStyleString.length() == 1) {
            return camelStyleString.toUpperCase();
        }
        String upperCaseString = "";
        upperCaseString += camelStyleString.charAt(0);
        for (int i = 1; i < camelStyleString.length(); i++) {
            if ((camelStyleString.charAt(i) >= 'A')
                    && (camelStyleString.charAt(i) <= 'Z')) {
                //如果为大写字母,说明为单词开头,则加下划线分割
                upperCaseString += "_";
            }
            upperCaseString += camelStyleString.charAt(i);
        }
        upperCaseString = upperCaseString.toUpperCase();
        return upperCaseString;
    }
}
